package Cinema.CinemaEntities;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import Utils.XmlParseHelper;
import Cinema.DataLayer.IXmlParsable;
import Cinema.DataLayer.XmlDefines;
import Exceptions.BadArgumentException;
import Exceptions.CinemaException;
import Exceptions.WrongXMLElement;


/**
 * Represents a theater(hall) in Cinema system.
 * @author The Fuchtzeinsoft Team.
 *
 */
public class Theater implements IXmlParsable, IMapItem {

	private String name;
	@Override
	public String getName() {
		return name;
	}

	private Location location;
	@Override
	public Location getLocation() {
		return location;
	}

	private String city;
	@Override
	public String getCity() {
		return city;
	}
	public void SetCity(String city){
		this.city =  city;
	}

	private HashSet<Show> Shows = new HashSet<Show>();
	public Set<Show> getShows(){

		HashSet<Show> updatedShows = null; //this is a lazy update, we will update the shows only if needed.
		for (Show show : Shows) {
			if (show.IsExpired()){

				if (updatedShows == null){ //need to perform update
					updatedShows = new HashSet<Show>(Shows);
					updatedShows.remove(show);
				}
				else{
					updatedShows.remove(show);
				}
			}
		}
		if (updatedShows!=null){
			Shows.clear();
			Shows = updatedShows;
		}
		return Shows;
	}

	private int rows;
	/**
	 * @return amount of rows in the theater.
	 */
	public int getRowAmount() {
		return rows;
	}

	private int seatsInRow;
	/**
	 * @return amount of seat in each row of the Theater.
	 */
	public int getRowSeatsAmount() {
		return seatsInRow;
	}

	/**
	 * construct a new Theater instance.
	 *
	 * @param name
	 *            - the name of the new Theater.
	 * @param location
	 *            - the location of the new Theater.
	 * @param rows
	 *            - the amount of rows in the Theater.
	 * @param seatsInRow
	 *            - the amount of seats in each row in the Theater.
	 * @throws CinemaException
	 *             - is case of bad parameters.
	 */
	public Theater(String name, Location location, int rows, int seatsInRow)
			throws CinemaException {
		if (rows < 0 || seatsInRow < 0) {
			throw new BadArgumentException(
					"an error accured while trying to construct"
							+ getClass().toString()
							+ ". a negative parameter given as rows/seatInRow amount");
		}
		this.name = name;
		this.location = location;
		this.rows = rows;
		this.seatsInRow = seatsInRow;
	}


	/**
	 * construct a new Theater instance.
	 *
	 * @param name
	 *            - the name of the new Theater.
	 * @param location
	 *            - the location of the new Theater.
	 * @param rows
	 *            - the amount of rows in the Theater.
	 * @param seatsInRow
	 *            - the amount of seats in each row in the Theater.
	 *
	 * @param myCity
	 *            - the city of the Theater. can be initialized with null.
	 * @throws CinemaException
	 *             - is case of bad parameters.
	 */
	public Theater(String name, Location location, int rows, int seatsInRow, String myCity)
			throws BadArgumentException {
		if (rows <= 0 || seatsInRow <= 0) {
			throw new BadArgumentException(
					"an error accured while trying to construct"
							+ getClass().toString()
							+ ". a not positive parameter given as rows/seatInRow amount");
		}
		this.name = name;
		this.location = location;
		this.rows = rows;
		this.seatsInRow = seatsInRow;
		this.city = myCity;
	}


	/**
	 * Construct a new Theater according to an XML element;
	 *
	 * @param xmlElement
	 *            - an XML element representing a theater.
	 * @throws WrongXMLElement
	 */
	public Theater(boolean isRestoring, Element xmlElement) throws WrongXMLElement {
		initFromXml(isRestoring, xmlElement);
	}

	@Override
	public Element getXml(Document document) {

		//create representing element
		Element theaterElement = document.createElement(XmlDefines.THEATER);

		//create field for name
		Element nameElement = document.createElement(XmlDefines.NAME);
		theaterElement.appendChild(nameElement);
		nameElement.setTextContent(name);

		//create field for city
		Element cityElement = document.createElement(XmlDefines.CITY);
		theaterElement.appendChild(cityElement);
		cityElement.setTextContent(city);

		//create field for location
		Element locationElement = document.createElement(XmlDefines.LOCATION);
		theaterElement.appendChild(locationElement);
		Element xLocationElement = document.createElement(XmlDefines.X_LOCATION);
		xLocationElement.setTextContent(new Integer(location.getX()).toString());
		locationElement.appendChild(xLocationElement);
		Element yLocationElement = document.createElement(XmlDefines.Y_LOCATION);
		yLocationElement.setTextContent(new Integer(location.getY()).toString());
		locationElement.appendChild(yLocationElement);

		//create and append seats data
		Element rowsElement = document.createElement(XmlDefines.ROWS_IN_THEATRE);
		rowsElement.setTextContent(new Integer(rows).toString());
		theaterElement.appendChild(rowsElement);
		Element seatsInRowElement = document.createElement(XmlDefines.SEATS_IN_ROW);
		seatsInRowElement.setTextContent(new Integer(seatsInRow).toString());
		theaterElement.appendChild(seatsInRowElement);


		//create and append show list
		Element showListElement = document.createElement(XmlDefines.SHOW_LIST);
		theaterElement.appendChild(showListElement);
		Iterator<Show> showIterator = getShows().iterator();
		while(showIterator.hasNext()){
			Show currentShow = showIterator.next();
			showListElement.appendChild(currentShow.getXml(document));
		}

		return theaterElement;
	}

	@Override
	public void initFromXml(Element xmlElement) throws WrongXMLElement {
	}

	@Override
	public void initFromXml(boolean isRestoring, Element xmlElement) throws WrongXMLElement {

		// verifying that write xml element given
		if (!xmlElement.getTagName().equals(XmlDefines.THEATER)) {
			throw new WrongXMLElement();
		}

		// init theater's name and city
		name = XmlParseHelper.getFirstChildByName(xmlElement,
				XmlDefines.NAME).getTextContent();
		city = XmlParseHelper.getFirstChildByName(xmlElement,
				XmlDefines.CITY).getTextContent();

		// init theater's seats
		String rowsString = XmlParseHelper.getFirstChildByName(xmlElement,
				XmlDefines.ROWS_IN_THEATRE).getTextContent();
		rows = Integer.parseInt(rowsString);
		String seatsInRowString = XmlParseHelper.getFirstChildByName(xmlElement,
				XmlDefines.SEATS_IN_ROW).getTextContent();
		seatsInRow = Integer.parseInt(seatsInRowString);

		// init theater location
		Element placeElement = XmlParseHelper.getFirstChildByName(xmlElement,
				XmlDefines.LOCATION);
		String xString = XmlParseHelper.getFirstChildByName(placeElement,
				XmlDefines.X_LOCATION).getTextContent();
		String yString = XmlParseHelper.getFirstChildByName(placeElement,
				XmlDefines.Y_LOCATION).getTextContent();
		location = new Location(Integer.parseInt(xString),
				Integer.parseInt(yString));

		if(isRestoring){
			//init from backup file

			Element shoeListElement = XmlParseHelper.getFirstChildByName(xmlElement, XmlDefines.SHOW_LIST);
			ArrayList<Element> showElements= XmlParseHelper.getChildrenByName(shoeListElement, XmlDefines.SHOW);
			for (Element element : showElements) {
				Shows.add(new Show(this,element));
			}

		}
		else{
			//init from initial xml

			// creating shows list - the xml is different in init and backup cases
			if (isRestoring) {
				ArrayList<Element> showElements = XmlParseHelper.getChildrenByName(
						xmlElement, XmlDefines.SHOW);
				for (Element element : showElements) {
					Shows.add(new Show(this,element));
				}
			}
			else {
				Element movieListElement = XmlParseHelper.getFirstChildByName(
						xmlElement, XmlDefines.MOVIE_LIST);
				ArrayList<Element> movieElements = XmlParseHelper.getChildrenByName(
						movieListElement, XmlDefines.MOVIE);
				// iterate over each movie
				for (Element element : movieElements) {

					String movieName = XmlParseHelper.getFirstChildByName(element, XmlDefines.NAME)
							.getTextContent();
					String movieGenre = XmlParseHelper.getFirstChildByName(element, XmlDefines.GENRE).getTextContent();

					Element priceElement = XmlParseHelper.getFirstChildByName(element, XmlDefines.PRICE);
					int adultPrice = Integer.parseInt(XmlParseHelper.getFirstChildByName(priceElement,
							XmlDefines.ADULT_PRICE).getTextContent());
					int childPrice = Integer.parseInt(XmlParseHelper.getFirstChildByName(priceElement,
							XmlDefines.CHILD_PRICE).getTextContent());

					Element timeListElement = XmlParseHelper.getFirstChildByName(element,
							XmlDefines.TIMES_LIST);
					ArrayList<Element> timeElements = XmlParseHelper.getChildrenByName(
							timeListElement,
							XmlDefines.TIME);
					// iterate over all times of projections
					for (Element element2 : timeElements) {

						// adding new show to the list
						Shows.add(new Show(XmlParseHelper.convertCinemaDateStringToDateObject(element2.getTextContent())
								,this ,movieName, movieGenre, adultPrice, childPrice));
					}
				}
			}// end of else
		}//end of main else - init from initial xml
	}

	/**
	 * check if two object are equals. used for tests only.
	 */
	public boolean checkEquals(Object obj) {
		if (obj.getClass()!=Theater.class){
			return false;
		}
		Theater theater = (Theater)obj;
		if (!city.equals(theater.city)){
			return false;
		}
		if (location.getX() != theater.location.getX()){
			return false;
		}
		if (location.getY() != theater.location.getY()){
			return false;
		}
		if (rows != theater.rows){
			return false;
		}
		if (seatsInRow != theater.seatsInRow){
			return false;
		}
		if (!name.equals(theater.name)){
			return false;
		}
		if (this.Shows.size() != theater.Shows.size()) {
			return false;
		}
		for (Show s : Shows) {
			boolean b = false;
			for (Show sOther : theater.Shows) {
				if (s.checkEquals(sOther)){
					b = true;
				}
			}
			if (b==false){
				return false;
			}
		}
		return true;
	}


}
